package gogo

import (
	"bufio"
	"errors"
	"fmt"
	"log"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

type XY struct {
	X, Y int
}

func NewXY(x, y int) XY {
	return XY{x, y}
}

type UB struct {
	Uid, Bid int
}

func NewUB(u, b int) UB {
	return UB{u, b}
}

type Gozi struct {
	X   int `json:"x"`
	Y   int `json:"y"`
	Uid int `json:"uid"`
}
type DebugInfoXY struct {
	X   int
	Y   int
	Bid int
	Uid int
}

func NewDebugInfoXY(xy XY, ub UB) DebugInfoXY {
	return DebugInfoXY{xy.X, xy.Y, ub.Bid, ub.Uid}
}

type DebugInfoBlock struct {
	Bid int
	XYs [][]int
	Qi  int
}
type DebugInfo struct {
	XYs    []DebugInfoXY
	Blocks []DebugInfoBlock
}

type block struct {
	Qi  int
	XYs Set[XY]
}

func newBlock() block {
	return block{0, NewSet[XY]()}
}

var mapXYUB = map[XY]UB{}
var mapBXYsQ = map[int]block{}
var curBid = 0

var mutex sync.RWMutex

var fileLogName = "gogo.journal"
var fileLog *log.Logger

func DebugState() {
	fmt.Println("===")
	fmt.Printf("curBid: %d\n", curBid)
	fmt.Printf("mapXYU\tx,y\t| uid\t| bid\n")
	for xy, ub := range mapXYUB {
		fmt.Printf("\t%d,%d\t| %d\t| %d\n", xy.X, xy.Y, ub.Uid, ub.Bid)
	}
	fmt.Printf("mapBQ\tbid\t| qi\t| [x,y]\n")
	for bid, b := range mapBXYsQ {
		fmt.Printf("\t%d\t| %d\t| ", bid, b.Qi)
		for xy := range b.XYs {
			fmt.Printf("%d,%d\t", xy.X, xy.Y)
		}
		fmt.Printf("\n")
	}
	// fmt.Println("===")
}
func GetDebugInfo() DebugInfo {
	our := []DebugInfoXY{}
	for xy, ub := range mapXYUB {
		our = append(our, NewDebugInfoXY(xy, ub))
	}
	bs := []DebugInfoBlock{}
	for b, bk := range mapBXYsQ {
		a := [][]int{}
		for xy := range bk.XYs {
			a = append(a, []int{xy.X, xy.Y})
		}
		bs = append(bs, DebugInfoBlock{b, a, bk.Qi})
	}
	return DebugInfo{our, bs}
}
func nextBid() int {
	curBid++
	return curBid
}

func findBid(bid int) (xys Set[XY], ok bool) {
	b, ok := mapBXYsQ[bid]
	return b.XYs, ok
}

func findNeis(xy XY) []XY {
	x, y := xy.X, xy.Y
	return []XY{
		{x - 1, y},
		{x + 1, y},
		{x, y - 1},
		{x, y + 1},
	}
}
func neisKinds(neis []XY, uid int) (empty Set[XY], our Set[XY], other Set[XY]) {
	empty, our, other = NewSet[XY](), NewSet[XY](), NewSet[XY]()
	for _, xy := range neis {
		ub, ok := mapXYUB[xy]
		if !ok {
			empty.Add(xy)
		} else if ub.Uid == uid {
			our.Add(xy)
		} else {
			other.Add(xy)
		}
	}
	return
}
func ziBids(xys Set[XY]) (bids Set[int]) {
	m := NewSet[int]()
	for xy := range xys {
		ub, ok := mapXYUB[xy]
		if ok {
			m.Add(ub.Bid)
		}
	}
	return m
}
func dryInsert(x, y int, uid int) int {
	xy := NewXY(x, y)
	ourbid := nextBid()
	ub := NewUB(uid, ourbid)
	mapXYUB[xy] = ub
	return ourbid
}
func Insert(x, y int, uid int) error {
	mutex.Lock()
	defer mutex.Unlock()

	err := insert0(x, y, uid)
	if err != nil {
		return err
	}
	fileLog.Printf("%d,%d %d\n", x, y, uid)
	return nil
}
func setBid(xy XY, bid int) {
	ub := mapXYUB[xy]
	ub.Bid = bid
	mapXYUB[xy] = ub
}
func setUid(xy XY, uid int) {
	ub := mapXYUB[xy]
	ub.Uid = uid
	mapXYUB[xy] = ub
}
func appendXY(bid int, xy XY) Set[XY] {
	b := mapBXYsQ[bid]
	b.XYs[xy] = true
	mapBXYsQ[bid] = b
	return b.XYs
}
func setXYs(bid int, xys Set[XY]) Set[XY] {
	b := mapBXYsQ[bid]
	b.XYs = xys
	mapBXYsQ[bid] = b
	return xys
}
func setQi(bid int, qi int) int {
	b := mapBXYsQ[bid]
	b.Qi = qi
	mapBXYsQ[bid] = b
	return qi
}
func setQiMinus(bid int) int {
	return setQi(bid, mapBXYsQ[bid].Qi-1)
}
func insert0(x, y int, uid int) error {

	xy := NewXY(x, y)
	if _, ok := mapXYUB[xy]; ok {
		return errors.New("x y already exists")
	}

	ourbid := dryInsert(x, y, uid)

	// === step 1 ===
	// xy就是我们刚刚下的子的坐标
	// 这一步是找到这个子的四个邻居（不论是己方，敌方，还是空）
	neis := findNeis(xy)
	// 这一步是将邻居分分类。
	// 空，己方，敌方
	empty, our, other := neisKinds(neis, uid)
	// 己方棋子分别属于那几个块呢？由下面这个函数回答
	bids := ziBids(our)
	// 如果有一个或者多于一个的块
	if len(bids) > 0 {
		// 那么就把这些块的bid统一成一个
		ourbid = updateBids(xy, bids)
		// 当然别忘记我们刚刚下的子
		setBid(xy, ourbid)
		// maybe we should count qi here
	}

	// === step 2 ===
	if _, ok := mapBXYsQ[ourbid]; !ok {
		mapBXYsQ[ourbid] = newBlock()
	}
	xys := appendXY(ourbid, xy)
	setQi(ourbid, countQi(xys))
	// determine will do step 2
	if !allUsAlive(len(empty), len(other)) {

		toDel := NewSet[int]()
		bids = ziBids(other)
		for bid := range bids {
			qi := setQiMinus(bid)
			if qi == 0 {
				toDel.Add(bid)
			}
		}

		if len(toDel) > 0 {
			reCalBids := needReCal(toDel)
			for bid := range toDel {
				delBlock(bid)
			}
			reCalBlocks(reCalBids)
		}

		// === step 3 ===
		qi := reCountQi(ourbid)
		if len(toDel) == 0 {
			if qi == 0 {
				reCalBids := needReCal(NewSet(ourbid))
				delBlock(ourbid)
				reCalBlocks(reCalBids)
			}
		}
	}
	return nil
}
func reCountQi(bid int) int {
	return setQi(bid, countQi(mapBXYsQ[bid].XYs))
}
func reCalBlocks(bids Set[int]) {
	for bid := range bids {
		reCountQi(bid)
	}
}

// 需要重新计算
// 被提子周围的棋子所在的块需要重新计算
func needReCal(toDel Set[int]) Set[int] {
	reCal := NewSet[int]() // bids
	for bid := range toDel {
		our, _ := findBid(bid)
		allNeis := NewSet[XY]()
		for xy := range our {
			allNeis.Add(findNeis(xy)...)
		}
		a := ziBids(allNeis)
		reCal.Union(a)
	}
	return reCal.Sub(toDel)
}

func delBlock(bid int) {
	xys, _ := findBid(bid)
	for xy := range xys {
		delete(mapXYUB, xy)
	}
	delete(mapBXYsQ, bid)
}
func allUsAlive(emptyN, otherN int) bool {
	return emptyN > 0 && otherN == 0
}
func countQi(xys Set[XY]) (n int) {
	m := map[XY]bool{}
	for xy := range xys {
		delete(m, xy)
		z := findNeis(xy)
		for _, k := range z {
			m[k] = true
		}
	}
	for xy := range m {
		if _, ok := mapXYUB[xy]; !ok {
			n += 1
		}
	}
	return n
}
func minInt(ints Set[int]) int {
	xs := ints.Slice()
	sort.Ints(xs)
	return xs[0]
}

// 合成一个块
func updateBids(xy XY, bids Set[int]) (bid int) {
	bid = minInt(bids)
	a := NewSet(xy)
	for b := range bids {
		setBidForAll(mapBXYsQ[b].XYs, bid)
		a.Union(mapBXYsQ[b].XYs)
		delete(mapBXYsQ, b)
	}
	setXYs(bid, a)
	return
}
func setBidForAll(xys Set[XY], bid int) {
	for xy := range xys {
		setBid(xy, bid)
	}
}

func GetRect(x1, y1, x2, y2 int) (a []Gozi) {
	a = []Gozi{}
	mutex.Lock()
	defer mutex.Unlock()
	for i := x1; i <= x2; i++ {
		for j := y1; j <= y2; j++ {
			if ub, ok := mapXYUB[NewXY(i, j)]; ok {
				a = append(a, Gozi{(i), (j), ub.Uid})
			}
		}
	}
	return
}

func Init(persist bool, lname string) *os.File {
	mapXYUB = map[XY]UB{}
	mapBXYsQ = map[int]block{}

	fileLogName = lname
	if persist {
		doPersisit()
	}
	f, err := os.OpenFile(fileLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}
	fileLog = log.New(f, "", log.LstdFlags)
	return f
}

var reInsert = regexp.MustCompile(`^INSERT INTO ZI\b`)
var reValue = regexp.MustCompile(`\bVALUES\s(.+);`)

func LoadFromSql(fname string) {
	f, err := os.Open(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	s := bufio.NewScanner(f)
	for s.Scan() {
		line := s.Bytes()
		if reInsert.Match(line) {
			log.Printf("%s\n", line)
			m := reValue.FindSubmatch(line)
			// log.Printf("%s\n", m)
			words := strings.Split(string(m[1]), " ")
			a := []int{}
			for _, e := range words {
				d, err := strconv.Atoi(strings.Trim(e, "(,)"))
				if err != nil {
					log.Fatal(err)
				}
				a = append(a, d)
			}
			// log.Printf("insert %v\n", a)
			Insert((a[1]), (a[2]), a[4])
		}
	}
}

func doPersisit() {
	f, err := os.Open(fileLogName)
	if err != nil {
		return
	}
	defer f.Close()

	t := time.Now()
	s := bufio.NewScanner(f)
	n := 0
	for s.Scan() {
		line := s.Text()
		a := strings.Split(line, " ")
		xy, uid := a[2], a[3]
		x, y := sep2int(xy)
		u := mustAtoi(uid)
		insert0(x, y, u)
		n++
	}
	d := time.Now().Sub(t)
	log.Printf("%d loaded in %s\n", n, d.String())
}
func sep2int(s string) (int, int) {
	a := strings.Split(s, ",")
	return mustAtoi(a[0]), mustAtoi(a[1])
}
func mustAtoi(s string) int {
	n, err := strconv.Atoi(s)
	if err != nil {
		log.Fatal(err)
	}
	return n
}

// todo func snapshot
